package cn.lyjuan.first.netty.demo._5_netty_heartbeat.client.handler;

import cn.lyjuan.first.netty.demo._5_netty_heartbeat.server.Server;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timeout;
import io.netty.util.Timer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.net.InetSocketAddress;
import java.util.concurrent.TimeUnit;

/**
 * 重连机制处理器
 */
public class ReconnectHandler extends ChannelInboundHandlerAdapter implements io.netty.util.TimerTask
{
    private static Logger log = LogManager.getLogger(ReconnectHandler.class);
    /**
     * 连接引导
     */
    private Bootstrap strap;

    /**
     * 定时器
     */
    private io.netty.util.Timer timer;

    /**
     * 服务器
     */
    private String host;

    /**
     * 端口
     */
    private int port;

    /**
     * 是否自动重连标识
     */
    private volatile boolean reconnect = true;

    /**
     * 重连间隔控制标识
     */
    private int attempts;

    /**
     *
     */
    private int attemptsCount;

    public ReconnectHandler(Bootstrap strap, String host, int port)
    {
        this.strap = strap;
        this.host = host;
        this.port = port;

        this.timer = new HashedWheelTimer();
    }


    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception
    {
        InetSocketAddress host = (InetSocketAddress) ctx.channel().remoteAddress();
        log.info("client connect to " + host.getHostName() + ":" + host.getPort());
        this.attempts = 0;
        this.attemptsCount = 0;
        super.channelActive(ctx);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception
    {
        String addr = host + ":" + port;
        log.info("client disconnect to " + addr);

        if (!this.reconnect)
        {
            log.info("client reconnect is disabled");
        } else
        {
            log.info("client reconnect(" + attemptsCount + ") to " + addr);
            if (attempts < 2)// 不再增加时间
                attempts++;

            int timeout = 2 << attempts;// 重连时间间隔指数级增长

            attemptsCount++;

            timer.newTimeout(this, timeout, TimeUnit.SECONDS);
        }

        super.channelInactive(ctx);
    }

    @Override
    public void run(Timeout timeout) throws Exception
    {
        log.info("client reconnecting to " + host + ":" + port);
        ChannelFuture future = null;
        synchronized (strap)
        {
            future = strap.connect(host, port);
        }

        future.addListener((ChannelFutureListener) future1 ->
        {
            log.debug("client channel future listener");
            String addr = host + ":" + port;
            if (future1.isSuccess())
            {
                log.info("client reconnect success to " + addr);
            } else
            {
                log.info("client reconnect failed to " + addr);
                future1.channel().pipeline().fireChannelInactive();// 重新触发失效事件
            }
        });

    }

    public Bootstrap getStrap()
    {
        return strap;
    }

    public void setStrap(Bootstrap strap)
    {
        this.strap = strap;
    }

    public Timer getTimer()
    {
        return timer;
    }

    public void setTimer(Timer timer)
    {
        this.timer = timer;
    }

    public String getHost()
    {
        return host;
    }

    public void setHost(String host)
    {
        this.host = host;
    }

    public int getPort()
    {
        return port;
    }

    public void setPort(int port)
    {
        this.port = port;
    }

    public boolean isReconnect()
    {
        return reconnect;
    }

    public void setReconnect(boolean reconnect)
    {
        this.reconnect = reconnect;
    }

    public int getAttempts()
    {
        return attempts;
    }

    public void setAttempts(int attempts)
    {
        this.attempts = attempts;
    }

    public int getAttemptsCount()
    {
        return attemptsCount;
    }

    public void setAttemptsCount(int attemptsCount)
    {
        this.attemptsCount = attemptsCount;
    }
}
